package org.xlp.db.xml;

import org.xlp.assertion.AssertUtils;
import org.xlp.db.sql.*;
import org.xlp.db.sql.table.Table;
import org.xlp.db.utils.BeanUtil;
import org.xlp.db.utils.SQLUtil;
import org.xlp.db.xml.ele.ParamConstants;
import org.xlp.db.xml.ele.ParamConstants.Connector;
import org.xlp.db.xml.ele.ParamConstants.Flag;
import org.xlp.db.xml.ele.ParamConstants.Op;
import org.xlp.db.xml.ele.ParamEle;
import org.xlp.db.xml.ele.SQLEle;
import org.xlp.javabean.JavaBeanPropertiesDescriptor;
import org.xlp.utils.XLPArrayUtil;
import org.xlp.utils.XLPStringUtil;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

/**
 * 把xml中的sql语句转换成SQL对象
 * 
 * @author 徐龙平
 * 
 * @version 1.0
 */
public class XmlSQL implements SQL, Delete, Update, Query, Statistics, Insert {
	/**
	 * 实体类型
	 */
	private Class<?> entityClass;
	/**
	 * 参数值集合
	 */
	private Object[] params;
	/**
	 * SQLEle对象
	 */
	private SQLEle sqlEle;
	/**
	 * SQL语句
	 */
	private String sql;

	/**
	 * 当SQLEle中的ParamEle对象为null时，即在xml文件中没param元素时，使用该构造函数
	 * 
	 * @param sqlEle
	 *            SQLEle对象
	 * @param params
	 *            预处理参数
	 * @param cs
	 *            实体类型
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public XmlSQL(SQLEle sqlEle, Object[] params, Class<?> cs) {
		AssertUtils.isNotNull(sqlEle, "sqlEle parameter must not be null!");
		this.sqlEle = sqlEle;
		this.params = params;
		this.entityClass = cs;
		this.sql = this.sqlEle.getSqlContent();
	}

	/**
	 * 当SQLEle中的ParamEle对象为null时，即在xml文件中没param元素时，使用该构造函数
	 * 
	 * @param sqlEle
	 *            SQLEle对象
	 * @param params
	 *            预处理参数
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public XmlSQL(SQLEle sqlEle, Object[] params) {
		this(sqlEle, params, null);
	}

	/**
	 * 当SQLEle中的ParamEle对象为null时，即在xml文件中没param元素时，使用该构造函数
	 * 
	 * @param sqlEle
	 *            SQLEle对象
	 * @param params
	 *            预处理参数
	 * @param cs
	 *            实体类型
	 * @throws NullPointerException
	 *             假如参数sqlEle或params为null，抛出该异常
	 */
	public XmlSQL(SQLEle sqlEle, List<Object> params, Class<?> cs) {
		this(sqlEle, params.toArray(), cs);
	}

	/**
	 * 当SQLEle中的ParamEle对象为null时，即在xml文件中没param元素时，使用该构造函数
	 * 
	 * @param sqlEle
	 *            SQLEle对象
	 * @param params
	 *            预处理参数
	 * @throws NullPointerException
	 *             假如参数sqlEle或params为null，抛出该异常
	 */
	public XmlSQL(SQLEle sqlEle, List<Object> params) {
		this(sqlEle, params, null);
	}

	/**
	 * 当SQLEle中的ParamEle对象为null时，即在xml文件中没param元素时，使用该构造函数
	 * 
	 * @param sqlEle
	 *            SQLEle对象
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public XmlSQL(SQLEle sqlEle) {
		this(sqlEle, (Object[]) null, null);
	}

	/**
	 * 当SQLEle中的ParamEle对象为null时，即在xml文件中没param元素时，使用该构造函数
	 * 
	 * @param sqlEle
	 *            SQLEle对象
	 * @param cs
	 *            实体类型
	 * @throws NullPointerException
	 *             假如参数cs或sqlEle为null，抛出该异常
	 */
	public XmlSQL(SQLEle sqlEle, Class<?> cs) {
		this(sqlEle, (Object[]) null, cs);
	}

	/**
	 * 当SQLEle中的ParamEle对象不为null时，即在xml文件中有param元素时，使用该构造函数
	 *
	 * @param sqlEle
	 *            SQLEle对象
	 * @param map
	 *            map预处理参数(可为null)
	 * @param obj
	 *            对象预处理参数(可为null)
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public <T> XmlSQL(SQLEle sqlEle, Map<String, Object> map, T obj)
			throws XmlSQLException {
		this(sqlEle, (Class<?>) null, map, obj);
	}

	/**
	 * 当SQLEle中的ParamEle对象不为null时，即在xml文件中有param元素时，使用该构造函数
	 *
	 * @param sqlEle
	 *            SQLEle对象
	 * @param cs
	 *            实体类型
	 * @param map
	 *            map预处理参数(可为null)
	 * @param obj
	 *            对象预处理参数(可为null)
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public <T> XmlSQL(SQLEle sqlEle, Class<?> cs, Map<String, Object> map, T obj)
			throws XmlSQLException {
		this(sqlEle, (Object[])null, cs, map, obj);
	}

	/**
	 * 当SQLEle中的ParamEle对象不为null时，即在xml文件中有param元素时，使用该构造函数
	 *
	 * @param sqlEle
	 *            SQLEle对象
	 * @param cs
	 *            实体类型
	 * @param params
	 * 	          预处理参数
	 * @param map
	 *            map预处理参数(可为null)
	 * @param obj
	 *            对象预处理参数(可为null)
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public <T> XmlSQL(SQLEle sqlEle, List<Object> params, Class<?> cs, Map<String, Object> map, T obj)
			throws XmlSQLException {
		this(sqlEle, params.toArray(), cs, map, obj);
	}

	/**
	 * 当SQLEle中的ParamEle对象不为null时，即在xml文件中有param元素时，使用该构造函数
	 * 
	 * @param sqlEle
	 *            SQLEle对象
	 * @param cs
	 *            实体类型
	 * @param params
	 * 	          预处理参数
	 * @param map
	 *            map预处理参数(可为null)
	 * @param obj
	 *            对象预处理参数(可为null)
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public <T> XmlSQL(SQLEle sqlEle, Object[] params, Class<?> cs, Map<String, Object> map, T obj)
			throws XmlSQLException {
		AssertUtils.isNotNull(sqlEle, "sqlEle parameter must not be null!");
		this.entityClass = cs;
		this.sqlEle = sqlEle;
		this.sql = this.sqlEle.getSqlContent();
		this.params = params;
		initParams(map, obj);
	}

	/**
	 * 当SQLEle中的ParamEle对象不为null时，即在xml文件中有param元素时，使用该构造函数
	 *
	 * @param sqlEle
	 *            SQLEle对象
	 * @param cs
	 *            实体类型
	 * @param params
	 * 	          预处理参数
	 * @param map
	 *            map预处理参数(可为null)
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public XmlSQL(SQLEle sqlEle, Object[] params, Class<?> cs, Map<String, Object> map)
			throws XmlSQLException {
		this(sqlEle, params, cs, map, null);
	}

	/**
	 * 当SQLEle中的ParamEle对象不为null时，即在xml文件中有param元素时，使用该构造函数
	 *
	 * @param sqlEle
	 *            SQLEle对象
	 * @param cs
	 *            实体类型
	 * @param params
	 * 	          预处理参数
	 * @param obj
	 *            对象预处理参数(可为null)
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public <T> XmlSQL(SQLEle sqlEle, Object[] params, Class<?> cs, T obj)
			throws XmlSQLException {
		this(sqlEle, params, cs, null, obj);
	}

	/**
	 * 当SQLEle中的ParamEle对象不为null时，即在xml文件中有param元素时，使用该构造函数
	 *
	 * @param sqlEle
	 *            SQLEle对象
	 * @param params
	 * 	          预处理参数
	 * @param map
	 *            map预处理参数(可为null)
	 * @param obj
	 *            对象预处理参数(可为null)
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public <T> XmlSQL(SQLEle sqlEle, Object[] params, Map<String, Object> map, T obj)
			throws XmlSQLException {
		this(sqlEle, params, null, map, obj);
	}

	/**
	 * 当SQLEle中的ParamEle对象不为null时，即在xml文件中有param元素时，使用该构造函数
	 *
	 * @param sqlEle
	 *            SQLEle对象
	 * @param params
	 * 	          预处理参数
	 * @param obj
	 *            对象预处理参数(可为null)
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public <T> XmlSQL(SQLEle sqlEle, Object[] params, T obj) throws XmlSQLException {
		this(sqlEle, params, (Map<String, Object>)null, obj);
	}

	/**
	 * 当SQLEle中的ParamEle对象不为null时，即在xml文件中有param元素时，使用该构造函数
	 *
	 * @param sqlEle
	 *            SQLEle对象
	 * @param map
	 *            map预处理参数(可为null)
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public XmlSQL(SQLEle sqlEle, Map<String, Object> map) throws XmlSQLException {
		this(sqlEle, (Object[])null, map, null);
	}

	/**
	 * 当SQLEle中的ParamEle对象不为null时，即在xml文件中有param元素时，使用该构造函数
	 *
	 * @param sqlEle
	 *            SQLEle对象
	 * @param obj
	 *            对象预处理参数(可为null)
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public <T> XmlSQL(SQLEle sqlEle, T obj) throws XmlSQLException {
		this(sqlEle, (Object[])null, (Map<String, Object>)null, obj);
	}

	/**
	 * 当SQLEle中的ParamEle对象不为null时，即在xml文件中有param元素时，使用该构造函数
	 *
	 * @param sqlEle
	 *            SQLEle对象
	 * @param cs
	 *            实体类型
	 * @param map
	 *            map预处理参数(可为null)
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public XmlSQL(SQLEle sqlEle, Class<?> cs, Map<String, Object> map)
			throws XmlSQLException {
		this(sqlEle, cs, map, null);
	}

	/**
	 * 当SQLEle中的ParamEle对象不为null时，即在xml文件中有param元素时，使用该构造函数
	 *
	 * @param sqlEle
	 *            SQLEle对象
	 * @param cs
	 *            实体类型
	 * @param obj
	 *            对象预处理参数(可为null)
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 * @throws NullPointerException
	 *             假如参数sqlEle为null，抛出该异常
	 */
	public <T> XmlSQL(SQLEle sqlEle, Class<?> cs, T obj)
			throws XmlSQLException {
		this(sqlEle, cs, null, obj);
	}

	/**
	 * @param map
	 * @param obj
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 */
	@SuppressWarnings("unchecked")
	private <T> void initParams(Map<String, Object> map, T obj)
			throws XmlSQLException {
		List<Object> paramList = new ArrayList<>();
		if (!XLPArrayUtil.isEmpty(this.params)){
			paramList.addAll(Arrays.asList(this.params));
		}

		JavaBeanPropertiesDescriptor<T> propertiesDescriptor = null;
		if (obj != null){
			propertiesDescriptor = new JavaBeanPropertiesDescriptor<>((Class<T>) obj.getClass());
		}

		List<ParamEle> paramEles = sqlEle.getParamEles();

		StringBuilder sb = new StringBuilder();
		for (ParamEle paramEle : paramEles) {
			boolean joint = paramEle.isJoint();
			// 判断是否拼接到已有的SQL语句后
			if (joint && !XLPStringUtil.isEmpty(paramEle.getCol())) {
				Op op = paramEle.getOp();
				if (op == null) {
					throw newXmlsqlException(sqlEle.getId(), "op");
				}

				Object value = getValue(map, obj, paramEle, propertiesDescriptor);
				//判断是否值是否为空，为空时，是否拼接到已有的sql后面
				if (XLPStringUtil.isNullOrEmpty(value) && !paramEle.isTrim()) {
					continue;
				}
				getAppendExtSql(sb, paramEle, value, paramList);
			} else {
				initEachParam(map, obj, paramList, paramEle, propertiesDescriptor);
			}
		}
		if (sb.length() > 0){
			sql += " " + sb;
		}
		params = paramList.toArray();
	}

	/**
	 * 获取额外的sql片段
	 * @param sb
	 * @param paramEle
	 * @param value
	 * @param paramList
	 */
	private void getAppendExtSql(StringBuilder sb, ParamEle paramEle, Object value,
								 List<Object> paramList) {
		Class<?> valueClass = value == null ? null : value.getClass();
		// 判断value是否是数组或集合
		if (valueClass != null && !paramEle.isTrim()) {
			if (valueClass.isArray() && Array.getLength(value) == 0) {
				return;
			}
			if (Collection.class.isAssignableFrom(valueClass)
					&& ((Collection<?>) value).isEmpty()) {
				return;
			}
		}

		Op op = paramEle.getOp();
		//获取连接符
		Connector connector = paramEle.getConnector();
		String connectorString = connector == null ? null : connector.getConnector();

		//把param元素的一些属性拼接的SQL中
		if (!XLPStringUtil.isNullOrEmpty(connectorString)) {
			sb.append(connectorString).append(" ");
		}
		//拼接左括号
		ParamConstants.Bracket bracket = paramEle.getBracket();
		if (bracket == ParamConstants.Bracket.lb){
			sb.append(bracket.getName());
		}

		if (op == Op.in || op == Op.not_in) {
			if (value == null || valueClass == null) {
				paramList.add(value);
				//拼接列名称
				sb.append(paramEle.getCol()).append(" ").append(op.getOp()).append(" (?)");
			} else {
				List<Object> params = new ArrayList<>();
				if (valueClass.isArray()) {
					int len = Array.getLength(value);
					for (int i = 0; i < len; i++) {
						params.add(Array.get(value, i));
					}
				} else if (Collection.class.isAssignableFrom(valueClass)) {
					Collection<?> c = (Collection<?>) value;
					params.addAll(c);
				} else {
					params.add(value);
				}
				paramList.addAll(params);
				//拼接列名称
				sb.append(SQLUtil.formatInSql(params.size(), SQL_IN_CONDITION_MAX_LEN, paramEle.getCol(),
						op == Op.in ? SQLUtil.InType.IN : SQLUtil.InType.NOT_IN));
			}
		} else {
			if (op == Op.like || op == Op.not_like) {
				String likeValue = String.valueOf(value)
						.replace("%", "\\%").replace("_", "\\_");
				paramList.add("%" + likeValue + "%");
			} else {
				paramList.add(value);
			}

			//拼接列名称
			sb.append(paramEle.getCol()).append(" ").append(op.getOp()).append(" ?");
		}

		//拼接右括号
		if (bracket == ParamConstants.Bracket.rb){
			sb.append(bracket.getName());
		}
	}

	/**
	 * 初始化每个预处理参数值
	 * 
	 * @param map
	 *            map预处理参数(可为null)
	 * @param obj
	 *            对象预处理参数(可为null)
	 * @param paramList
	 *            参数集合
	 * @param paramEle
	 *            <code>ParamEle</code>对象
	 * @param propertiesDescriptor
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 */
	private <T> void initEachParam(Map<String, Object> map, T obj,
			List<Object> paramList, ParamEle paramEle, JavaBeanPropertiesDescriptor<T> propertiesDescriptor)
			throws XmlSQLException {
		Object value = getValue(map, obj, paramEle, propertiesDescriptor);
		if (value != null){
			Class<?> valueClass = value.getClass();
			//处理集合
			if (Collection.class.isAssignableFrom(valueClass)){
				paramList.addAll((Collection<?>) value);
			} else if (valueClass.isArray()){
				//处理数组
				int len = Array.getLength(value);
				for (int i = 0; i < len; i++) {
					paramList.add(Array.get(value, i));
				}
			} else {
				paramList.add(value);
			}
		} else {
			paramList.add(null);
		}
	}

	/**
	 * 获取值
	 * 
	 * @param map
	 *            map预处理参数(可为null)
	 * @param obj
	 *            对象预处理参数(可为null)
	 * @param paramEle
	 *            <code>ParamEle</code>对象
	 * @param propertiesDescriptor
	 * @throws XmlSQLException
	 *             假如SQL语句解析出错，抛出该异常
	 */
	@SuppressWarnings("all")
	private Object getValue(Map<String, Object> map, Object obj,
			ParamEle paramEle, JavaBeanPropertiesDescriptor<?> propertiesDescriptor) throws XmlSQLException {
		Flag flag = paramEle.getFlag();
		if (flag == null) {
			throw newXmlsqlException(sqlEle.getId(), "flag");
		}
		//获取字段名称
		String fn = paramEle.getFn();
		if (XLPStringUtil.isEmpty(fn)) {
			throw newXmlsqlException(sqlEle.getId(), "fn");
		}

		Object value = null;
		if (flag == Flag.map) {
			//从map获取指定key值
			value = map == null ? null : map.get(fn);
		} else {
			if (obj != null && propertiesDescriptor != null) {
				Object _obj = obj;
				//获取对象相应的字段值
				String[] fns = fn.split("\\.");
				JavaBeanPropertiesDescriptor _propertiesDescriptor;
				for (int i = 0; i < fns.length; i++) {
					if (i == 0){
						_propertiesDescriptor = propertiesDescriptor;
					} else {
						_propertiesDescriptor = new JavaBeanPropertiesDescriptor(_obj.getClass());
					}
					value = BeanUtil.callGetter(_obj, _propertiesDescriptor.getDescriptor(fns[i]));
					if (value == null) break;
					_obj = value;
				}
			}
		}
		//处理默认值
		String defaultValue = paramEle.getDefaultV();
		// 判断是否使用默认值
		if (XLPStringUtil.isNullOrEmpty(value) && !XLPStringUtil.isNullOrEmpty(defaultValue)) {
			value = toDefaultValue(defaultValue, paramEle);
		}
		return value;
	}

	/**
	 * 处理默认值
	 * @param defaultValue
	 * @param paramEle
	 * @return
	 */
	private Object toDefaultValue(String defaultValue, ParamEle paramEle) {
		switch (paramEle.getType()){
			case INT:
				return Integer.parseInt(defaultValue);
			case LONG:
				return Long.parseLong(defaultValue);
			case BIGINT:
				return new BigInteger(defaultValue);
			case DOUBLE:
				return Double.parseDouble(defaultValue);
			case BIGDECIMAL:
				return new BigDecimal(defaultValue);
			default:
				return defaultValue;
		}
	}

	/**
	 * 设置实体类型
	 * @param entityClass
	 */
	public void setEntityClass(Class<?> entityClass){
		this.entityClass = entityClass;
	}

	@Override
	public Class<?> getEntityClass() {
		return entityClass;
	}

	@Override
	public String getParamSql() {
		String gb = sqlEle.getGroupBy();
		sql = XLPStringUtil.isEmpty(gb) ? sql : sql + " group by " + gb;
		String ob = sqlEle.getOrderBy();
		sql = XLPStringUtil.isEmpty(ob) ? sql : sql + " order by " + ob;
		return sql;
	}

	@Override
	public Object[] getParams() {
		return params;
	}

	@Override
	public String getSql() {
		String sql = SQLUtil
				.fillWithParams(new StringBuilder(this.sql), params);
		String gb = sqlEle.getGroupBy();
		sql = XLPStringUtil.isEmpty(gb) ? sql : sql + " group by " + gb;
		String ob = sqlEle.getOrderBy();
		sql = XLPStringUtil.isEmpty(ob) ? sql : sql + " order by " + ob;
		return sql;
	}

	@Override
	public String toString() {
		return getSql();
	}

	private XmlSQLException newXmlsqlException(String sqlId, String param) {
		return new XmlSQLException("id为" + sqlId + "元素中的param元素的属性[" + param
				+ "]值不存在或为空");
	}

	/**
	 * 获取查询SQL对象对应的标对象
	 *
	 * @return 始终返回null
	 */
	@Override
	public Table<?> getTable() {
		return null;
	}
}
